/* eslint-disable no-use-before-define */
const STATUS = {
  PENDING: 'pending',
  FULLFILLED: 'fulfilled',
  REJECTED: 'rejected',
};

class MyPromise {
  constructor(executor) {
    this.status = STATUS.PENDING; // promise 状态
    this.value = undefined; // 成功之后的值
    this.reason = undefined; // 失败之后的原因
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    this.then = this.then.bind(this);
    this.finally = this.finally.bind(this);
    this.catch = this.catch.bind(this);
    this.successCallback = []; // 成功回调
    this.failCallback = []; // 失败回调
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }

  static resolve(data) {
    return new MyPromise((resolveCall, reject) => {
      if (data instanceof MyPromise) {
        data.then(resolveCall, reject);
      } else {
        resolveCall(data);
      }
    });
  }

  static reject(data) {
    return new MyPromise((resolve, rejectCall) => {
      if (data instanceof MyPromise) {
        data.then(resolve, rejectCall);
      } else {
        rejectCall(data);
      }
    });
  }

  static race(array) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < array.length; i += 1) {
        const task = array[i];
        if (task instanceof MyPromise) {
          task.then(resolve, reject);
        } else {
          resolve(task);
        }
      }
    });
  }

  static all(array) {
    let index = 0;
    const result = [];

    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value;
        index += 1;
        if (index === array.length) {
          resolve(result);
        }
      }
      for (let i = 0; i < array.length; i += 1) {
        const task = array[i];
        if (task instanceof MyPromise) {
          task.then(
            value => addData(i, value),
            reason => reject(reason),
          );
        } else {
          addData(i, array[i]);
        }
      }
    });
  }

  resolve(data) {
    if (this.status !== STATUS.PENDING) {
      return;
    }
    this.status = STATUS.FULLFILLED;
    this.value = data;
    // 判断成功回调是否存在，存在即调用
    while (this.successCallback[0]) {
      this.successCallback.shift()(this.value);
    }
  }

  reject(reason) {
    if (this.status !== STATUS.PENDING) {
      return;
    }
    this.status = STATUS.REJECTED;
    this.reason = reason;
    // 判断失败回调是否存在，存在即调用
    if (this.failCallback[0]) {
      this.failCallback.shift()(this.reason);
    }
  }

  then(
    successCallback = value => value,
    failCallback = reason => {
      throw reason;
    },
  ) {
    const nextPromise = new MyPromise((resolve, reject) => {
      // 判断状态
      switch (this.status) {
        case STATUS.FULLFILLED: {
          thenCallback(successCallback(this.value), nextPromise, resolve, reject);
          break;
        }
        case STATUS.REJECTED: {
          thenCallback(failCallback(this.reason), nextPromise, resolve, reject);
          break;
        }
        default:
          this.successCallback.push(() => {
            thenCallback(successCallback(this.value), nextPromise, resolve, reject);
          });
          this.failCallback.push(() => {
            thenCallback(failCallback(this.reason), nextPromise, resolve, reject);
          });
          break;
      }
    });
    return nextPromise;
  }

  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason =>
        MyPromise.resolve(callback()).then(() => {
          throw reason;
        }),
    );
  }

  catch(failCallback) {
    this.then(undefined, failCallback);
  }
}

// 判断成功回调的返回值是普通值还是 promise 对象
// 如果是 promise 对象，查看 promise 对象的返回结果
// 再根据 promise 对象的返回结果决定调用成功回调还是失败回调
// 如果是普通值，直接调用 resolve
function thenCallback(returnValue, nextPromise, resolve, reject) {
  setTimeout(() => {
    try {
      if (returnValue === nextPromise) {
        reject(new TypeError('A promise cannot be resolved with itself.'));
        return;
      }
      if (returnValue instanceof MyPromise) {
        returnValue.then(resolve, reject);
      } else {
        resolve(returnValue);
      }
    } catch (error) {
      reject(error);
    }
  }, 0);
}

module.exports = MyPromise;
